import sys
sys.path.append("/home/thomas/SpyderProjects/")
sys.path.append("/home/thomas/SpyderProjects/pycalendar/")

from datetime import date, datetime, timedelta
from pycalendar.loggers import myLogger

#create logger for module
module_logger = myLogger.TLogger(__name__)

__all__ = ["HolidayProfile", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", "January", "February", "March",
            "April", "May", "June", "July", "August", "September", "October", "November", "December", "dayoffset"]

#gives names to calendar numbers for readability and to use globally within module
Monday      = 1
Tuesday     = 2
Wednesday   = 3
Thursday    = 4
Friday      = 5
Saturday    = 6
Sunday      = 7

January     = 1
February    = 2
March       = 3
April       = 4
May         = 5
June        = 6
July        = 7
August      = 8
September   = 9
October     = 10
November    = 11
December    = 12

dayoffset   = timedelta(1)

def holidayprofiles():
    """Scans all subdirectories of this module for holiday profiles and adds them to a dictionary together with their doc string.

    >>> holidayprofiles() 
    """
    


class _Singleton(type):
    """Need a Singleton class to make all HolidayProfiles singletons.
       Each HolidayProfile can only bee instantiated once.
       HolidayProfile defines a metaclass that uses this SIngleton class."""
    
    def __call__(self, *args, **kwargs):
        if 'instance' not in self.__dict__:
            self.instance = super(_Singleton, self).__call__(*args, **kwargs)
        return self.instance
        
class HolidayProfile(object):

    """HolidayProfile is the superclass for all calendars. Per default Saturday and Sunday are weekends and are not business days. 
       This needs to be overwritten for Arabic countries where Friday and Saturday are weekends.
       No national holidays are declared in this base class. These can be added in subclass by overwriting the isHoliday() function.
       Contains a function to calculate Easter for a given year. This is often needed for Western calendars and was put in the base class
       so that all subclasses inherit the function.
       It also contains a function to calculate the Vernal and Autumn Equinox which is needed for Asian calendars.

       >>> h = HolidayProfile()
       >>> HolyNight_Tuesday = datetime(2008, 12, 24)
       >>> h.isWeekend(HolyNight_Tuesday)
       False
       >>> h.isHoliday(HolyNight_Tuesday)
       False
       >>> h.isBusinessDay(HolyNight_Tuesday)
       True
       >>> NewYear_Sunday = datetime(2012, 1, 1)
       >>> h.isWeekend(NewYear_Sunday)
       True
       >>> h.isHoliday(NewYear_Sunday)
       False
       >>> h.isBusinessDay(NewYear_Sunday)
       False
       >>> IndependenceDay_Wednesday = datetime(2012, 7, 4)
       >>> h.isWeekend(IndependenceDay_Wednesday)
       False
       >>> h.isHoliday(IndependenceDay_Wednesday)
       False
       >>> h.isBusinessDay(IndependenceDay_Wednesday)
       True
       >>> h.EasterSunday(2012)
       datetime.date(2012, 4, 8)
       >>> h.EasterMonday(2012)
       datetime.date(2012, 4, 9)
       >>> h.GoodFriday(2012)
       datetime.date(2012, 4, 6)
       >>> h.holiday(NewYear_Sunday)
       >>> h.isHoliday(NewYear_Sunday)
       False
    """

    #__metaclass__ = _Singleton # see http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python
    
    weekend = []
        
    holidaydict = {}

#    def __init__(self):
#        self.logger = myLogger.TLogger(self.__class__.__name__)
#        self.logger.debug("Creating holiday profile: {}".format(self.__class__.__name__))
    
    @classmethod
    def isWeekend(cls, date):
        """In this base class Weekend is defined as Saturday and Sunday. Some Arabic and Asian countries
        have different weekends, e.g. Saudi Arabia. For those this method needs to be overwritten. """
        return date.isoweekday() in cls.weekend

    @classmethod
    def isBusinessDay(cls, date):
        """If not weekend or holiday it's a business day."""
        return not(cls.isWeekend(date) or cls.isHoliday(date))
    
    @staticmethod
    def EasterSunday(year):
        """Returns Easter as a date object.
        Uses the Butcher alogrithm. The date for Easter is needed for most Western calendars."""
        a = year % 19
        b = year // 100
        c = year % 100
        d = (19 * a + b - b // 4 - ((b - (b + 8) // 25 + 1) // 3) + 15) % 30
        e = (32 + 2 * (b % 4) + 2 * (c // 4) - d - (c % 4)) % 7
        f = d + e - 7 * ((a + 11 * d + 22 * e) // 451) + 114
        month = f // 31
        day = f % 31 + 1
        return date(year, month, day)
     
    @classmethod
    def EasterMonday(cls, year):
        return cls.EasterSunday(year) + timedelta(days=1)
    
    @classmethod
    def GoodFriday(cls, year):
        return cls.EasterSunday(year) + timedelta(days=-2)
    
    @classmethod
    def VernalEquinox(cls, year):
        """Returns vernal equinox as date object.
        Needed for some Eastern calendars."""
        exact_vernal_equinox_time = 20.69115
        ve = cls.__Equinox(exact_vernal_equinox_time, year)
        return date(year, March, ve)
    
    @classmethod
    def AutumnalEquinox(cls, year):
        """Returns autunal equinox as date object.
        Needed for some Eastern calendars."""
        exact_autumnal_equinox_time = 23.09
        ae = cls.__Equinox(exact_autumnal_equinox_time, year)
        return date(year, September, ae)
    
    @classmethod
    def __Equinox(cls, equinoxtime, y):
        diff_per_year = 0.242194;
        moving_amount = (y-2000)*diff_per_year
        number_of_leap_years = (y-2000)//4+(y-2000)//100-(y-2000)//400
        equinoxday = equinoxtime + moving_amount - number_of_leap_years
        return int(equinoxday)

    @classmethod        
    def holiday(cls, date, returnNameandRule = False, returnMultipleHolidays = False):
        w  = date.isoweekday() #where Monday is 1 and Sunday is 7
        m  = date.month          #Between 1 and 12 inclusive.
        d  = date.day    #Between 1 and the number of days in the month
        y  = date.year            #Between MINYEAR and MAXYEAR inclusive.
        es = cls.EasterSunday(y)
        em = cls.EasterMonday(y)
        gf = cls.GoodFriday(y)
        ae = cls.AutumnalEquinox(y)
        ve = cls.VernalEquinox(y)
        
        if returnNameandRule:
            results = [(hname, hrule) for (hname,hrule) in cls.holidaydict.items() if eval(hrule)]
        else:
            results = [hname for (hname,hrule) in cls.holidaydict.items() if eval(hrule)]
        
        if results == []:
            return None
        elif returnMultipleHolidays: #return a list of all matching holidays
            return results
        else:
            return results[0] # return first matching holiday

    @classmethod
    def isHoliday(cls, date):
        return not cls.holiday(date) is None

        
if __name__ == "__main__" :
    #sys.path.insert(0, '../..')
    #module_logger.info('Running {}'.format(__name__))
    import doctest
    doctest.testmod(verbose=True)
